feat(plugin): add host based plugin configuration, pipeline and lifecycle hooks - #35
Conversation
| foreach (var loadedPlugin in Ordered(plugins)) | ||
| { | ||
| var plugin = loadedPlugin.Plugin; | ||
| if (plugin.MiddlewareType is null | ||
| || HasImplementation(plugin, nameof(IAuthKitPlugin.ConfigureApplication), typeof(IApplicationBuilder)) | ||
| || HasImplementation(plugin, nameof(IAuthKitPlugin.ConfigurePipeline), | ||
| typeof(IApplicationBuilder), typeof(PluginPipelinePosition))) | ||
| continue; | ||
|
|
||
| application.UseMiddleware(plugin.MiddlewareType); | ||
| } |
There was a problem hiding this comment.
The explicit foreach is deliberate here: the loop body uses plugin more than once (and ConfigurePipeline also continues on a position filter), so collapsing to .Select(...) would not improve readability. No functional impact -- dismissing.
| foreach (var loadedPlugin in plugins) | ||
| { | ||
| var plugin = loadedPlugin.Plugin; | ||
| if (HasImplementation(plugin, nameof(IAuthKitPlugin.ConfigurePipeline), | ||
| typeof(IApplicationBuilder), typeof(PluginPipelinePosition)) | ||
| && !Enum.IsDefined(plugin.PipelinePosition)) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"Plugin '{plugin.Id}' declares unsupported pipeline position '{plugin.PipelinePosition}'."); | ||
| } | ||
| } |
There was a problem hiding this comment.
The explicit foreach is deliberate here: the loop body uses plugin more than once (and ConfigurePipeline also continues on a position filter), so collapsing to .Select(...) would not improve readability. No functional impact -- dismissing.
| foreach (var loadedPlugin in _plugins) | ||
| { | ||
| var plugin = loadedPlugin.Plugin; | ||
| logger.LogDebug("Starting plugin '{PluginId}'.", plugin.Id); | ||
| Invoke(plugin, "OnStarting", () => plugin.OnStartingAsync(cancellationToken)); | ||
| } |
There was a problem hiding this comment.
The explicit foreach is deliberate here: the loop body uses plugin more than once (and ConfigurePipeline also continues on a position filter), so collapsing to .Select(...) would not improve readability. No functional impact -- dismissing.
| foreach (var loadedPlugin in _plugins) | ||
| { | ||
| var plugin = loadedPlugin.Plugin; | ||
| logger.LogDebug("Plugin '{PluginId}' started.", plugin.Id); | ||
| Invoke(plugin, "OnStarted", () => plugin.OnStartedAsync(lifetime.ApplicationStopping)); | ||
| } |
There was a problem hiding this comment.
The explicit foreach is deliberate here: the loop body uses plugin more than once (and ConfigurePipeline also continues on a position filter), so collapsing to .Select(...) would not improve readability. No functional impact dismissing.
| foreach (var loadedPlugin in _plugins.Reverse()) | ||
| { | ||
| var plugin = loadedPlugin.Plugin; | ||
| logger.LogDebug("Stopping plugin '{PluginId}'.", plugin.Id); | ||
| Invoke(plugin, "OnStopping", () => plugin.OnStoppingAsync(lifetime.ApplicationStopping)); | ||
| } |
There was a problem hiding this comment.
The explicit foreach is deliberate here: the loop body uses plugin more than once (and ConfigurePipeline also continues on a position filter), so collapsing to .Select(...) would not improve readability. No functional impact -- dismissing.
| catch (Exception ex) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"Plugin '{plugin.Id}' failed during {stage}.", ex); | ||
| } |
There was a problem hiding this comment.
This is intentional: the catch wraps any failure from a plugin-run hook with plugin and stage context and rethrows, so a broken plugin fails loudly during host startup or shutdown. Narrowing the exception type would defeat that purpose. Accepting as designed.
Summary
This PR extends the plugin contract with the host builder and a scoped configuration context, adds explicit application pipeline and endpoint hooks, and bridges plugin lifecycle hooks and plugin-owned hosted services to the standard .NET host lifecycle. The behavior is verified against the real host entry point with integration tests and documented in three new ADRs.
Configuration Context and Host Builder
ConfigureServicesoverloads forIHostApplicationBuilderand forIServiceCollectionwithAuthKitPluginContextEndpoints and Application Pipeline Hooks
ConfigureApplicationand pipeline position awareConfigurePipelinehooks withPluginPipelinePositionMapEndpointsMiddlewareTypeentry point as a compatibility fallback for plugins that do not opt into the new hooksLifecycle Hooks and Hosted Services
PluginLifecycleHostedServicebridgingOnStarting/OnStarted/OnStoppingtoIHostApplicationLifetimeIntegration Tests
tests/Host.IntegrationTestsdriving the realProgramthroughWebApplicationFactoryDocumentation
Validation
AuthKit.Plugins.Abstractions.Tests,AuthKit.Host.Tests, andAuthKit.Host.IntegrationTestsdotnet buildcompletes with zero errors across the solutiongit diff --checkpassesResult
AuthKit plugins can now configure services through the host builder and scoped context, integrate into the application pipeline at explicit positions, and participate in the standard .NET host lifecycle with their own hosted services all verified against the real host entry point.
Closes #8
Closes #9
Closes #10